/************************************************************************

								Heroes IV
					  Copyright 2002, The 3DO Company

	------------------------------------------------------------------
  						       score_computer.cpp

	$Header: /game/score_computer.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include <algorithm>

#include "score_computer.h"

#include "adventure_frame.h"
#include "adventure_map.h"
#include "army.h"
#include "army_array.h"
#include "caravan.h"
#include "creature_array.h"
#include "creature_traits.h"
#include "debug_message.h"
#include "difficulty_level.h"
#include "elements_of.h"
#include "enum_operations.h"
#include "material_array.h"
#include "material_names.h"
#include "ownable_garrisonable_adv_object.h"
#include "player.h"
#include "sanctuary.h"
#include "town.h"


namespace
{
	//
	// NOTE: all these tables 'could' be moved to an init file if needed
	//

	// points awarded based on map size vs. difficulty level
	int const k_map_points[k_map_size_count][k_difficulty_count] = {
	//   Novc Int  Adv  Exp  Chmp
		{ 50, 100, 200, 400, 750 },		// Small
		{ 75, 125, 225, 420, 700 },		// Medium
		{ 90, 150, 250, 440, 650 },		// Large
		{ 100,200, 300, 450, 600 }		// Extra Large
	};

	// points awarded per castle base on map size
	int const k_max_castle_points = 120;
	int const k_castle_points[k_map_size_count] = { 30, 15, 10, 10 };

	// points for quests completed
	int const k_max_quest_points = 96;
	int const k_quest_points[k_map_size_count] = { 48, 24, 12, 8 };

	// points awarded for artifacts collected
	// artifact types that count for points: treasure, minor, major, relic, divine
	int const k_max_artifact_points = 76; 
	int const k_artifact_points[k_map_size_count][5] = {
		{ 10, 20, 40, 76, 76 },
		{  5, 10, 20, 45, 45 },
		{  1,  5, 10, 45, 45 },
		{  0,  3,  8, 30, 30 }
	};

	// points awarded for hero level, note an extra map size is included
	// for two different XL map size score types
	int const k_max_hero_level_points = 76;
	int const k_hero_level_required[k_map_size_count+1] = { 3, 4, 5, 6, 7 };
	int const k_hero_level_points[k_map_size_count+1] = { 10, 6, 5, 3, 2 };

	// amount of points awarded for each battle according to the 
	// ratio of creatures in combat
	int const k_max_battle_points = 72;

	// score point values for wealth vs. map size
	int const k_max_wealth_points = 48;
	float const k_wealth_points[k_map_size_count] = { 0.002f, 0.001f, 0.0005f,  0.00025f };
	
	// score point values for army size vs. map size
	int const k_max_army_points = 24;
	float const k_army_points[k_map_size_count] =   { 0.001f,  0.0005f, 0.00025f, 0.000125f };
	
	// exploration points
	int const k_max_exploration_points = 12;

	struct week_range 
	{
		unsigned	min_week;
		unsigned	max_week;
	};

	// score vs. time modifiers
	int const k_time_bonus[] = { 250, 100, 50 };

	int const k_time_max_week_slots = 13;
	int	const k_maximum_score_time = 8; // in weeks.
	int const k_minimum_score_percentage = 30; // in percent
	int const k_loss_per_week = 5; // in percent
	int const k_map_size_time_factor[k_map_size_count] = { 1, 2, 4, 6 };

	// map difficulty score modifiers
	int k_map_difficulty_score_percentages[k_difficulty_count] 
		= { 50, 75, 100, 125, 150 };

} // un-named namespace


// ---------------------------------------------------------------------
// calculate the score value for an artifact
// ---------------------------------------------------------------------
static int get_artifact_value( t_artifact const & artifact, t_map_size map_size )
{
	int point_value = 0;

	if( artifact.get_icon() != k_artifact_none )
	{
		switch( artifact.get_level() )
		{
		case k_artifact_level_treasure:
			point_value = k_artifact_points[map_size][0];
			break;
		case k_artifact_level_minor:
			point_value = k_artifact_points[map_size][1];
			break;
		case k_artifact_level_major:
			point_value = k_artifact_points[map_size][2];
			break;
		case k_artifact_level_relic:
			point_value = k_artifact_points[map_size][3];
			break;
		case k_artifact_level_divine:
			point_value = k_artifact_points[map_size][4];
			break;
		default:
			point_value = 0;
			break;
		}
	}

	return point_value;
}


// ---------------------------------------------------------------------
// Helper function to tally the value of an army
// ---------------------------------------------------------------------
static void tally_army( t_creature_array const & creatures, t_player & player, 
					    t_map_size map_size, t_final_scores & score_data )
{
	// Get the best hero
	t_hero const * hero = creatures.get_strongest_hero();
	if( hero )
	{
		if( !score_data.strongest_hero ||
			( hero->get_level() > score_data.strongest_hero->get_level() ) )
		{
			score_data.strongest_hero = hero;
			
			if( !player.is_computer() )
				score_data.human_player = &player;
		}
		
		score_data.hero_name = score_data.strongest_hero->get_name();
	}
	
	for( int j = 0; j < t_creature_array::k_size; ++j )
	{
		t_creature_stack const& creature_stack = creatures[j];
		
		if( creature_stack.get_number() <= 0 )
			continue;
		
		// gold value of owned creatures
		// hero's are not counted
		if( !creature_stack.get_const_hero() )
		{
			score_data.total_creature_gold_value += 
				creature_stack.get_number() * get_traits( creature_stack.get_creature_type() ).cost[k_gold];
		}
		
		//
		// artifact points
		//
		
		// check equiped slots
		t_artifact_slot k;
		int value;
		for( k = k_artifact_slot_bow; k < k_artifact_slot_hero_count; enum_incr( k ) )
		{
			value =	get_artifact_value( creature_stack.get_artifact( k ), map_size );
			if( value > 0 )
			{
				++score_data.artifacts_collected;
				score_data.artifact_points += value;
			}
		}
		
		// check backpacks
		int l;
		for( l = 0; l < creature_stack.get_backpack_count(); ++l )
		{
			value = get_artifact_value( creature_stack.get_backpack_slot( l ), map_size );
			if( value > 0 )
			{
				++score_data.artifacts_collected;
				score_data.artifact_points += value;
			}
		}
	}
}

// ---------------------------------------------------------------------
// compute army size for player
// ---------------------------------------------------------------------
static void tally_player_armies( t_player& player, t_final_scores& score_data,
								 t_map_size	map_size )
{
	// figure out the total gold value for all the creatures owned by the winning army
	{
		t_army_array const* armies = player.get_armies();
		assert( armies );
		t_army_array::const_iterator it;
		for ( it = armies->begin(); it != armies->end(); it++ )
			tally_army( *it->get(), player, map_size, score_data );
	}

	{
		t_town_list const * towns = player.get_towns();
		assert( towns );
		t_town_list::const_iterator it;
		for ( it = towns->begin(); it != towns->end(); it++ )
			tally_army( *it->get(), player, map_size, score_data );
	}

	{
		t_ownable_garrisonable_adv_object_list const & garrisons = player.get_garrisons();
		t_ownable_garrisonable_adv_object_list::const_iterator it;
		for ( it = garrisons.begin(); it != garrisons.end(); it++ )
			tally_army( *it->get(), player, map_size, score_data );
	}

	{
		t_sanctuary_list const & sanctuaries = player.get_sanctuary_list();
		t_sanctuary_list::const_iterator it;
		for ( it = sanctuaries.begin(); it != sanctuaries.end(); it++ )
			tally_army( *it->get(), player, map_size, score_data );
	}

	{
		t_caravan_set const & caravans = player.get_caravans();
		t_caravan_set::const_iterator it;
		for ( it = caravans.begin(); it != caravans.end(); it++ )
			tally_army( *it->get(), player, map_size, score_data );
	}


	// add up the total number of towns owned by the team
	score_data.town_count += player.get_towns()->size();


	// add up the quests completed per team
	score_data.quests_completed += player.get_quests_completed_count();

	// add up the battle score
	score_data.battles_won += player.get_battles_won();
	score_data.battles_won_points += player.get_battle_score();
}


// ---------------------------------------------------------------------
// calculate all the final score information
// ---------------------------------------------------------------------
void calculate_scores( t_adventure_map const & map, int winning_team, 
					   t_final_scores & score_data )
{
	int i;
	t_map_size			map_size = map.get_map_size_type();
	t_difficulty		map_difficulty = map.get_map_difficulty();
	t_difficulty		player_difficulty = map.get_player_difficulty();
	t_material_array	team_budget;

	// zero out	the scores struct
	score_data.army_size_points = 0;
	score_data.artifact_points = 0;
	score_data.artifacts_collected = 0;
	score_data.battles_won = 0;
	score_data.battles_won_points = 0;
	score_data.creature_index = k_creature_none;
	score_data.exploration_points = 0;
	score_data.final_score = 0;
	score_data.human_player = NULL;
	score_data.main_hero_points = 0;
	score_data.quest_points = 0;
	score_data.quests_completed = 0;
	score_data.resource_value = 0;
	score_data.strongest_hero = NULL;
	score_data.time_points = 0;
	score_data.total_creature_gold_value = 0;
	score_data.town_count = 0;
	score_data.town_points = 0;
	score_data.wealth_points = 0;

	// Store how many days it took to win the map
	score_data.days = map.get_day();

	team_budget -= get_initial_budget( player_difficulty );
	// tally up various counts
	for( i = 0; i < map.get_player_count(); ++i )
	{
		t_player & player = map.get_player( i );

		// save off the reference to the human player
		// in a single player game there is only one, we'll try to use the best hero player
		if ( !player.is_computer() )
			score_data.human_player = &player;

		if ( player.get_team() == winning_team )
		{
			tally_player_armies( player, score_data, map_size );
			// add up the total team funds
			team_budget += player.get_funds();
		}
	}

	// player difficulty
	int basic_victory_points = k_map_points[map_size][player_difficulty];

	// actually, this is the raw value, but we're not going to tell the player that.
	score_data.player_difficulty_percentage = basic_victory_points;

	// town points
	score_data.town_points = minimum( k_castle_points[map_size] * score_data.town_count, 
									  k_max_castle_points );

	// army size points
	score_data.army_size_points 
		= int(score_data.total_creature_gold_value * k_army_points[map_size] );
	score_data.army_size_points = minimum( score_data.army_size_points, k_max_army_points );

	// quest points
	score_data.quest_points = minimum( score_data.quests_completed * k_quest_points[map_size],
									   k_max_quest_points );

	// bounds check artifact points
	score_data.artifact_points = minimum( score_data.artifact_points, k_max_artifact_points );

	if ( score_data.strongest_hero)
	{
		int adjusted_map_size;
		if( map_size == k_map_size_extra_large && map.get_num_levels() > 1 )
			adjusted_map_size = map_size + 1;
		else
			adjusted_map_size = map_size;

		int multiplier = score_data.strongest_hero->get_level() - k_hero_level_required[adjusted_map_size];
		if (multiplier > 0)
			score_data.main_hero_points = minimum( multiplier * k_hero_level_points[adjusted_map_size], k_max_hero_level_points );
	}

	// battles won points
	score_data.battles_won_points = minimum( score_data.battles_won_points, 
											 k_max_battle_points );

	// wealth points
	int total_wealth = 0;

	total_wealth += team_budget[k_gold];
	for (i = k_wood; i < k_material_count; ++i)
		total_wealth += team_budget[i] * k_material_value[i] / 2;
	if (total_wealth < 0)
		total_wealth = 0;
	score_data.resource_value = total_wealth;
	total_wealth *= k_wealth_points[map_size]; // convert the wealth to points
	score_data.wealth_points = minimum( total_wealth, k_max_wealth_points );

	score_data.percent_explored = map.get_percent_explored( winning_team );
	score_data.exploration_points = score_data.percent_explored * k_max_exploration_points;

	score_data.map_difficulty_percentage = k_map_difficulty_score_percentages[map_difficulty];


	// time points
	unsigned week = map.get_day() / k_days_per_week;

	week = week / k_map_size_time_factor[ map_size ];
	score_data.time_points = score_data.player_difficulty_percentage;

	if (week < ELEMENTS_OF( k_time_bonus ))
		score_data.time_points += k_time_bonus[week];
	else if (week + 1 > k_maximum_score_time)
	{
		int	score_percentage = 100 - k_loss_per_week * (week + 1 - k_maximum_score_time);

		if (score_percentage < k_minimum_score_percentage)
			score_percentage = k_minimum_score_percentage;
		score_data.time_points = score_data.time_points * score_percentage / 100;
	}

	score_data.time_points = score_data.time_points 
						   * score_data.map_difficulty_percentage / 100;


	// calculate the unadjusted total game score
	score_data.final_score =  score_data.army_size_points;
	score_data.final_score += score_data.artifact_points;
	score_data.final_score += score_data.battles_won_points;
	score_data.final_score += score_data.exploration_points;
	score_data.final_score += score_data.main_hero_points;
	score_data.final_score += score_data.quest_points;
	score_data.final_score += score_data.time_points;
	score_data.final_score += score_data.town_points;
	score_data.final_score += score_data.wealth_points;

	// Bound the final score
	if (score_data.final_score < 0)
		score_data.final_score = 0;

	score_data.creature_index = get_rank_creature( score_data.final_score, k_creature_score_range );

	// Player cheated. No points
	if ( map.is_cheater() )
	{
		// zero out	the scores
		score_data.army_size_points = 0;
		score_data.artifact_points = 0;
		score_data.battles_won_points = 0;
		score_data.exploration_points = 0;
		score_data.final_score = 0;
		score_data.main_hero_points = 0;
		score_data.quest_points = 0;
		score_data.time_points = 0;
		score_data.town_points = 0;
		score_data.wealth_points = 0;
	}
}

// ---------------------------------------------------------------------
// compute the appropriate creature rank for a score
// ---------------------------------------------------------------------
namespace
{
	struct t_compare_creature_values
	{
		bool operator()( t_creature_type left, t_creature_type right ) const
		{
			t_creature_traits const&	left_traits = get_traits( left );
			t_creature_traits const&	right_traits = get_traits( right );
			int							left_experience = left_traits.experience;
			int							right_experience = right_traits.experience;

			if (left_experience != right_experience)
				return left_experience < right_experience;
			return left < right;
		}
	};
};

t_creature_type get_rank_creature( int score, int max_score )
{
	t_creature_type rank_list[k_creature_type_count];
	t_creature_type	creature;

	// sort creatures by value, from least to greatest
	for (creature = t_creature_type(0); creature < k_creature_type_count;
	     enum_incr( creature ))
		rank_list[creature] = creature;

	std::sort( rank_list, rank_list + k_creature_type_count, t_compare_creature_values());

	// divide scores from 0 to max_score into segments corresponding to the creatures
	// in rank order.
	int index = (score * (k_creature_type_count - 1) + (max_score>>1)) / max_score;

	// Make sure we don't accidently go out of bounds
	if (index >= k_creature_type_count)
		index = k_creature_type_count - 1;
	if (index < 0)
		index = 0;

	return rank_list[index];
}
